using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Diagnostics; // Stopwatch
using System.Security.Cryptography; // MD5
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;

public class Main : MonoBehaviour
{
    public Slider progressSlider;
    public Text progressLbl;

    public Button uploadBtn;
    public Button downloadBtn;
    public Button stopDownloadBtn;

    private const string SERVER_URL = "http://localhost:8988";
    private const string UPLOAD_FILE_NAME = "upload_test.png";     // 在 Assets/ 下
    private const string DOWNLOAD_FILE_NAME = "download_test.png"; // 服务器 downloads/ 下

    private IEnumerator m_downloadCoroutine = null;
    private Stream m_serverResponseStream = null;
    private FileStream m_localFileStream = null;

    // 一帧读128字节：方便观察断点续传（刻意很慢）
    private readonly byte[] m_downloadBuffer = new byte[128];

    [Serializable]
    public class UploadResp
    {
        public bool ok;
        public string file;
        public long size;
        public string desc;
        public string md5;
        public string saved_path;
        public string url;         // 老字段名
        public string url_hint;    // 新脚本里的提示字段
        public string server_time;
        public float elapsed_sec;
    }

    void Start()
    {
        Debug.Log($"[INIT] Server={SERVER_URL}  UploadFile={UPLOAD_FILE_NAME}  DownloadFile={DOWNLOAD_FILE_NAME}");

        uploadBtn.onClick.AddListener(() =>
        {
            var filePath = Path.Combine(Application.dataPath, UPLOAD_FILE_NAME);
            if (!File.Exists(filePath))
            {
                Debug.LogError($"[UPLOAD] Local file not found: {filePath}");
                return;
            }

            byte[] data = ReadLocalFile(filePath);
            Debug.Log($"[UPLOAD] Start file={UPLOAD_FILE_NAME} size={PrettyBytes(data.Length)} md5={BytesMD5Hex(data)}");
            StartCoroutine(UploadFile(SERVER_URL, Path.GetFileName(filePath), data));
        });

        downloadBtn.onClick.AddListener(() =>
        {
            StopDownload();
            m_downloadCoroutine = DownloadFile(SERVER_URL, DOWNLOAD_FILE_NAME);
            StartCoroutine(m_downloadCoroutine);
        });

        stopDownloadBtn.onClick.AddListener(StopDownload);
    }

    void OnDestroy() => StopDownload();

    private void StopDownload()
    {
        if (m_downloadCoroutine != null)
        {
            StopCoroutine(m_downloadCoroutine);
            m_downloadCoroutine = null;
        }
        if (m_serverResponseStream != null)
        {
            try { m_serverResponseStream.Close(); } catch { }
            try { m_serverResponseStream.Dispose(); } catch { }
            m_serverResponseStream = null;
        }
        if (m_localFileStream != null)
        {
            try { m_localFileStream.Close(); } catch { }
            try { m_localFileStream.Dispose(); } catch { }
            m_localFileStream = null;
        }
        Debug.Log("[DOWNLOAD] Stopped/Cleaned.");
    }

    // ---------------- Upload ----------------

    private static byte[] ReadLocalFile(string filePath)
    {
        using (FileStream fs = File.OpenRead(filePath))
        {
            byte[] data = new byte[fs.Length];
            int index = 0;
            const int chunk = 1024;
            while (index < data.Length)
            {
                int read = fs.Read(data, index, Math.Min(chunk, data.Length - index));
                if (read <= 0) break;
                index += read;
            }
            UnityEngine.Debug.Log($"[UPLOAD] Read Done {PrettyBytes(data.Length)} from {filePath}");
            return data;
        }
    }

    IEnumerator UploadFile(string url, string fileName, byte[] data)
    {
        var sw = Stopwatch.StartNew();

        var form = new WWWForm();
        form.AddField("desc", "test upload file");
        form.AddBinaryData("file_data", data, fileName, "application/octet-stream");

        var request = UnityWebRequest.Post(url, form);
        request.chunkedTransfer = false; // 关键：让服务端拿到 Content-Length
        Debug.Log("[UPLOAD] chunkedTransfer = false");

        var op = request.SendWebRequest();
        float lastPctLogged = -1f;

        while (!op.isDone)
        {
            progressSlider.value = request.uploadProgress;
            progressLbl.text = Math.Floor(request.uploadProgress * 100) + "%";

            // 每提升10%打印一次
            float pct = Mathf.Floor(request.uploadProgress * 10f) / 10f;
            if (pct > lastPctLogged)
            {
                lastPctLogged = pct;
                Debug.Log($"[UPLOAD] progress={Mathf.RoundToInt(request.uploadProgress * 100)}%");
            }
            yield return null;
        }

        sw.Stop();

        // —— 结果与响应头 —— //
        if (request.result != UnityWebRequest.Result.Success)
        {
            Debug.LogError($"[UPLOAD] FAIL code={request.responseCode} err={request.error}\n{request.downloadHandler.text}");
            yield break;
        }

        LogResponseHeaders("[UPLOAD] headers", request);

        var json = request.downloadHandler.text;
        Debug.Log($"[UPLOAD] Raw JSON:\n{json}");

        UploadResp resp = null;
        try { resp = JsonUtility.FromJson<UploadResp>(json); } catch (Exception e) { Debug.LogWarning(e); }

        if (resp != null && resp.ok)
        {
            Debug.Log(
                $"[UPLOAD OK] file={resp.file} size={PrettyBytes(resp.size)} md5={resp.md5}\n" +
                $"saved={resp.saved_path}\n" +
                $"url={(string.IsNullOrEmpty(resp.url) ? resp.url_hint : resp.url)}\n" +
                $"server_time={resp.server_time} elapsed={resp.elapsed_sec}s  rtt={sw.ElapsedMilliseconds}ms");
        }
        else
        {
            Debug.LogWarning("[UPLOAD] Parsed JSON missing or ok=false");
        }
    }

    // ---------------- Download ----------------

    IEnumerator DownloadFile(string url, string fileName)
    {
        Debug.Log($"[DOWNLOAD] Start: {url}/{fileName}");

        // 1) HEAD：获取总长度
        var swHead = Stopwatch.StartNew();
        UnityWebRequest huwr = UnityWebRequest.Head(url + "/" + fileName);
        yield return huwr.SendWebRequest();
        swHead.Stop();

#if UNITY_2020_2_OR_NEWER
        if (huwr.result != UnityWebRequest.Result.Success)
#else
        if (huwr.isNetworkError || huwr.isHttpError)
#endif
        {
            Debug.LogError($"[DOWNLOAD] HEAD failed: code={huwr.responseCode} err={huwr.error}");
            yield break;
        }

        LogResponseHeaders("[DOWNLOAD][HEAD] headers", huwr);

        long totalLength = 0;
        long.TryParse(huwr.GetResponseHeader("Content-Length"), out totalLength);
        Debug.Log($"[DOWNLOAD] TotalLength={totalLength} ({PrettyBytes(totalLength)})  headRTT={swHead.ElapsedMilliseconds}ms");

#if UNITY_EDITOR
        var filePath = Path.Combine(Application.streamingAssetsPath, fileName);
#else
        var filePath = Path.Combine(Application.persistentDataPath, fileName);
#endif
        Directory.CreateDirectory(Path.GetDirectoryName(filePath));

        using (m_localFileStream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
        {
            long nowLen = m_localFileStream.Length;
            Debug.Log($"[DOWNLOAD] Local file: {filePath}  currentLen={nowLen}");

            if (nowLen < totalLength)
            {
                m_localFileStream.Seek(nowLen, SeekOrigin.Begin);
                Debug.Log($"[DOWNLOAD] Resume from offset={nowLen}");
            }
            else if (nowLen == totalLength)
            {
                Debug.Log($"[DOWNLOAD] Already complete ({PrettyBytes(nowLen)})");
                progressSlider.value = 1f;
                progressLbl.text = "100%";
                yield break;
            }
            else
            {
                Debug.LogWarning("[DOWNLOAD] Local file larger than remote. Reset and re-download.");
                m_localFileStream.SetLength(0);
                nowLen = 0;
            }

            var req = (HttpWebRequest)WebRequest.Create(url + "/" + fileName);
            req.AddRange(nowLen); // 断点续传
            Debug.Log($"[DOWNLOAD] HTTP Range: bytes={nowLen}-");

            var swGet = Stopwatch.StartNew();
            HttpWebResponse resp;
            try
            {
                resp = (HttpWebResponse)req.GetResponse();
            }
            catch (WebException we)
            {
                Debug.LogError($"[DOWNLOAD] GetResponse error: {we.Message}");
                yield break;
            }

            using (resp)
            using (m_serverResponseStream = resp.GetResponseStream())
            {
                Debug.Log($"[DOWNLOAD] Status={(int)resp.StatusCode} {resp.StatusCode}  Content-Length={resp.ContentLength}");

                int read;
                long lastLoggedPct = -1;
                while ((read = m_serverResponseStream.Read(m_downloadBuffer, 0, m_downloadBuffer.Length)) > 0)
                {
                    m_localFileStream.Write(m_downloadBuffer, 0, read);
                    nowLen += read;

                    float pct = totalLength > 0 ? (float)nowLen / totalLength : 0f;
                    progressSlider.value = pct;
                    progressLbl.text = Math.Floor(pct * 100) + "%";

                    // 每提升10%打印一次
                    long nowPct10 = (long)Math.Floor(pct * 10f);
                    if (nowPct10 > lastLoggedPct)
                    {
                        lastLoggedPct = nowPct10;
                        Debug.Log($"[DOWNLOAD] progress={Mathf.RoundToInt(pct * 100)}%  {PrettyBytes(nowLen)}/{PrettyBytes(totalLength)}");
                    }

                    yield return null;
                }
                swGet.Stop();
            }

            Debug.Log($"[DOWNLOAD] Done -> {filePath}  size={PrettyBytes(m_localFileStream.Length)}  time={PrettyMs(swGet.ElapsedMilliseconds)}");
        }

        // 计算本地 MD5（可选）
#if UNITY_EDITOR || DEVELOPMENT_BUILD
        try
        {
#if UNITY_EDITOR
            var path = Path.Combine(Application.streamingAssetsPath, fileName);
#else
            var path = Path.Combine(Application.persistentDataPath, fileName);
#endif
            var md5 = FileMD5Hex(path);
            Debug.Log($"[DOWNLOAD] Local MD5: {md5}");
        }
        catch (Exception e) { Debug.LogWarning($"[DOWNLOAD] MD5 error: {e.Message}"); }
#endif
    }

    // ---------------- Helpers ----------------

    private static string PrettyBytes(long bytes)
    {
        if (bytes < 1024) return bytes + " B";
        double kb = bytes / 1024.0;
        if (kb < 1024) return kb.ToString("F1") + " KB";
        double mb = kb / 1024.0;
        if (mb < 1024) return mb.ToString("F2") + " MB";
        double gb = mb / 1024.0;
        return gb.ToString("F2") + " GB";
    }

    private static string PrettyMs(long ms) => ms + " ms";

    private static string BytesMD5Hex(byte[] data)
    {
        using (var md5 = MD5.Create())
        {
            var hash = md5.ComputeHash(data);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }

    private static string FileMD5Hex(string filePath)
    {
        using (var md5 = MD5.Create())
        using (var fs = File.OpenRead(filePath))
        {
            var hash = md5.ComputeHash(fs);
            return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
        }
    }

    private static void LogResponseHeaders(string tag, UnityWebRequest req)
    {
        Dictionary<string, string> headers = req.GetResponseHeaders();
        if (headers == null)
        {
            Debug.Log($"{tag}: <no headers>");
            return;
        }

        var sb = new System.Text.StringBuilder();
        sb.AppendLine(tag + ":");
        foreach (var kv in headers)
            sb.AppendLine($"  {kv.Key}: {kv.Value}");
        Debug.Log(sb.ToString());
    }
}
